package com.lplb.gateway.filter;

import com.lplb.api.admin.model.vo.AdminAuthVO;
import com.lplb.common.core.constant.base.BaseConstant;
import com.lplb.common.core.result.R;
import com.lplb.common.core.result.RType;
import com.lplb.common.core.utils.auth.JwtUtil;
import com.lplb.gateway.config.properties.AuthProperties;
import com.lplb.gateway.config.error.ErrorException;
import com.lplb.gateway.feign.RAdminAuthServiceFeign;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


/**
 * 统一登录认证/授权验证入口 ，过滤器注入和顺序指定：可参考  https://www.cnblogs.com/westlin/p/10911945.html
 * @author wangsong
 * @mail 1720696548@qq.com
 * @date 2020/7/20 0020 18:05
 * @version 1.0.0
 */
@SuppressWarnings("all")
@Slf4j
@Component
@NoArgsConstructor
@RefreshScope  //authProperties 需要获取最新数据
public class AuthFilter implements GlobalFilter, Ordered {

    // 过滤执行顺序，负数可获取正常的url请求接口，大于等于0接口前服务名将被过滤
    @Override
    public int getOrder() {
        return -2;
    }

    // 放行接口配置
    @Autowired
    private AuthProperties authProperties;

    // 接口资源
    @Autowired
    private RAdminAuthServiceFeign rAdminAuthServiceFeign;

    // 系统接口资源-刷新间隔时间
    private static Integer REFRESH_JG_TIME = 1000 * 30;
    // 系统接口资源Jvm缓存
    private static List<String> sysRoles = new ArrayList<>();
    // 下次刷新时间(时间戳)
    private static Long refreshTime = 0L;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String path = request.getPath().pathWithinApplication().value();
        InetAddress address = request.getRemoteAddress().getAddress();
        int port = request.getRemoteAddress().getPort();
        // 1、判断是否为swagger文档，是直接放行，v2/api-docs || 2、判断接口是否为放行接口,是直接放行, yml中配置放行接口
        if (this.isCheckSwagger(request) || this.isCheckRelease(request)) {
            return chain.filter(exchange);
        }
        // 3、是否登录
        if (!this.isCheckLogin(request)) {
            throw new ErrorException(RType.SYS_ERROR_401.getCode(), RType.SYS_ERROR_401.getMsg(), "没有Token");
        }
        // 4、是否过期，是否伪造token
        try {
            this.isCheckLoginExpired(request);
        } catch (ExpiredJwtException ex) {
            //System.out.println("Token过期时间: " + ex.getClaims().getExpiration());
            throw new ErrorException(RType.SYS_ERROR_401.getCode(), RType.SYS_ERROR_401.getMsg(), "Token已过期");
        } catch (Exception ex) {
            //token错误/解析失败
            throw new ErrorException(RType.SYS_ERROR_401.getCode(), RType.SYS_ERROR_401.getMsg(), "Token解析失败");
        }
        // 5、判断授权： type= 1-管理端 2-用户端 3-开放接口
        Integer type = JwtUtil.getType(request.getHeaders().get(BaseConstant.Sys.TOKEN).get(0));
        if (type == BaseConstant.Sys.LOGIN_TYPE_PT) {
            //管理端
            if (!this.isPtCheckManagementSideInterface(request)) {
                throw new ErrorException(RType.SYS_ERROR_403.getCode(), RType.SYS_ERROR_403.getMsg(), "没有接口权限");
            }
        }
        return chain.filter(exchange);
    }


    /**
     * 判断是否为swagger接口，是直接放行
     *
     * @return true 需要toket验证，false 不需要toket验证
     * @author ws
     * @mail 1720696548@qq.com
     * @date 2020/2/12 0012 15:00
     */
    private boolean isCheckSwagger(ServerHttpRequest request) {
        return request.getURI().toString().indexOf("v2/api-docs") != -1;
    }


    /**
     * 判断是否为放行接口 --> 判断接口是否需要验证, yml中配置放行接口
     *
     * @return true 需要toket验证，false 不需要toket验证
     * @author ws
     * @mail 1720696548@qq.com
     * @date 2020/2/12 0012 15:00 是否放行
     */
    private boolean isCheckRelease(ServerHttpRequest request) {
        // 放行接口yml配置数据
        Map<String, String> interfacesMap = authProperties.getInterfaceNoCheck();
        List<String> interfacesList = new ArrayList<>();
        if (interfacesMap != null && interfacesMap.size() > 0) {
            interfacesMap.forEach((k, v) -> interfacesList.add(v));
        }
        // 当前访问接口
        String interfaces = request.getURI().getPath();
        // 判断是否为放行接口
        boolean result = interfacesList.contains(interfaces);
        log.debug("请求地址:【{}】是否为放行接口: {}", interfaces, result);
        return result;
    }


    /**
     * 判断是否登录
     * @author wangsong
     * @param request
     * @date 2020/7/21 0021 9:30
     * @return boolean  ==> true 未登陆
     * @version 1.0.0
     */
    private boolean isCheckLogin(ServerHttpRequest request) {
        // 是否登录
        List<String> tokens = request.getHeaders().get(BaseConstant.Sys.TOKEN);
        return tokens != null || tokens.size() > 0;
    }


    /**
     *  判断登录是否过期
     * @author wangsong
     * @param request
     * @date 2020/7/21 0021 9:51
     * @return boolean ==> false 已过期
     * @version 1.0.0
     */
    private boolean isCheckLoginExpired(ServerHttpRequest request) {
        return JwtUtil.isExpiration(request.getHeaders().get(BaseConstant.Sys.TOKEN).get(0));
    }


    /**
     *  管理端接口验证 （ token验证+接口授权验证）
     *
     * @return true 需要toket验证，false 不需要toket验证
     * @author ws
     * @mail 1720696548@qq.com
     * @date 2020/2/12 0012 15:00
     */

    private boolean isPtCheckManagementSideInterface(ServerHttpRequest request) {
        // 服务接口/当前访问接口
        String interfaces = request.getPath().pathWithinApplication().value();
        // 用户权限
        List<String> userRoles = JwtUtil.getRoles(request.getHeaders().get(BaseConstant.Sys.TOKEN).get(0));
        // 系统权限
        this.refreshAuths();
        // 系统资源列表中不存在直接放行:
        if (sysRoles.contains(interfaces)) {
            return true;
        }
        //用户资源未空没有权限
        if (userRoles == null) {
            return false;
        }
        //验证用户资源是否存在当前访问接口
        if (userRoles.contains(interfaces)) {
            return true;
        } else {
            return false;
        }
    }


    /**
     * 刷新系统接口资源列表 ，sysRoles ( RPC 获取）
     * @author wangsong
     * @date 2020/7/21 0021 10:22
     * @return java.util.List<com.lplb.api.admin.model.vo.AdminAuthVO>
     * @version 1.0.0
     */
    private void refreshAuths() {
        //判断是否需要重新获取
        if (refreshTime - System.currentTimeMillis() > 0) {
            //未到刷新时间不刷新
        } else {
            R<List<AdminAuthVO>> result = rAdminAuthServiceFeign.findList();
            if (!result.getCode().equals(RType.SYS_SUCCESS.getCode())) {
                throw new ErrorException(result.getCode(), result.getMsg(), result.getErrorMsg());
            }
            // 数据处理
            List<AdminAuthVO> resultRoles = result.getData();
            List newSysRoles = new ArrayList<>();
            for (int i = 0; i < resultRoles.size(); i++) {
                newSysRoles.add(resultRoles.get(i).getServerName() + resultRoles.get(i).getUrl());
                log.debug("接口-{}: server:[{}]  ==>  interfaces:[{}] ==>   desc:[{}] ", i, resultRoles.get(i).getServerName(), resultRoles.get(i).getServerName() + resultRoles.get(i).getUrl(), resultRoles.get(i).getDesc());
            }
            sysRoles = newSysRoles;
            //下次刷新时间
            refreshTime = System.currentTimeMillis() + REFRESH_JG_TIME;
            log.debug(" ==========>>> 初始化/刷新系统权限列表完成, 下次刷新时间: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(refreshTime));
        }
    }


}